home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / ufw / frontend.py < prev    next >
Text File  |  2009-09-23  |  41KB  |  1,111 lines

  1. #
  2. # frontend.py: frontend interface for ufw
  3. #
  4. # Copyright 2008-2009 Canonical Ltd.
  5. #
  6. #    This program is free software: you can redistribute it and/or modify
  7. #    it under the terms of the GNU General Public License version 3,
  8. #    as published by the Free Software Foundation.
  9. #
  10. #    This program is distributed in the hope that it will be useful,
  11. #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #    GNU General Public License for more details.
  14. #
  15. #    You should have received a copy of the GNU General Public License
  16. #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17. #
  18.  
  19. import re
  20. import os
  21. import sys
  22. import warnings
  23.  
  24. from ufw.common import UFWError
  25. import ufw.util
  26. from ufw.util import error, warn
  27. from ufw.backend_iptables import UFWBackendIptables
  28.  
  29. def allowed_command(cmd):
  30.     '''Return command if it is allowed, otherwise raise an exception'''
  31.     allowed_cmds = ['enable', 'disable', 'help', '--help', 'default', \
  32.                     'logging', 'status', 'version', '--version', 'allow', \
  33.                     'deny', 'reject', 'limit', 'reload', 'show' ]
  34.  
  35.     if not cmd.lower() in allowed_cmds:
  36.         raise ValueError()
  37.  
  38.     return cmd.lower()
  39.  
  40. def parse_command(argv):
  41.     '''Parse command. Returns tuple for action, rule, ip_version and dryrun.'''
  42.     action = ""
  43.     rule = ""
  44.     type = ""
  45.     from_type = "any"
  46.     to_type = "any"
  47.     from_service = ""
  48.     to_service = ""
  49.     dryrun = False
  50.     insert_pos = ""
  51.     logtype = ""
  52.     loglevel = ""
  53.  
  54.     if len(argv) > 1 and argv[1].lower() == "--dry-run":
  55.         dryrun = True
  56.         argv.remove(argv[1])
  57.  
  58.     remove = False
  59.     if len(argv) > 1 and argv[1].lower() == "delete":
  60.         remove = True
  61.         argv.remove(argv[1])
  62.  
  63.     nargs = len(argv)
  64.  
  65.     if nargs < 2:
  66.         raise ValueError()
  67.  
  68.     if argv[1].lower() in ['insert']:
  69.         action = argv[1].lower()
  70.     else:
  71.         action = allowed_command(argv[1])
  72.  
  73.     if action == "insert":
  74.         if nargs < 4:
  75.             raise ValueError()
  76.         insert_pos = argv[2]
  77.  
  78.         # Using position '0' adds rule at end, which is potentially confusing
  79.         # for the end user
  80.         if insert_pos == "0":
  81.             err_msg = _("Cannot insert rule at position '%s'") % (insert_pos)
  82.             raise UFWError(err_msg)
  83.  
  84.         # strip out 'insert NUM' and parse as normal
  85.         del argv[2]
  86.         del argv[1]
  87.         action = allowed_command(argv[1])
  88.         nargs = len(argv)
  89.  
  90.         # error if use insert with non-rule commands
  91.         if action != "allow" and action != "deny" and action != "reject" and \
  92.            action != "limit":
  93.             raise ValueError()
  94.  
  95.     if action == "logging":
  96.         if nargs < 3:
  97.             raise ValueError()
  98.         elif argv[2].lower() == "off":
  99.             action = "logging-off"
  100.         elif argv[2].lower() == "on" or argv[2].lower() == "low" or \
  101.              argv[2].lower() == "medium" or argv[2].lower() == "high" or \
  102.              argv[2].lower() == "full":
  103.             action = "logging-on"
  104.             if argv[2].lower() != "on":
  105.                 action += "_" + argv[2].lower()
  106.         else:
  107.             raise ValueError()
  108.  
  109.     if action == "status" and nargs > 2:
  110.         if argv[2].lower() == "verbose":
  111.             action = "status-verbose"
  112.         elif argv[2].lower() == "numbered":
  113.             action = "status-numbered"
  114.  
  115.     if action == "show":
  116.         if nargs == 2:
  117.             raise ValueError()
  118.         elif argv[2].lower() == "raw":
  119.             action = "show-raw"
  120.  
  121.     if action == "default":
  122.         direction = "incoming"
  123.         if nargs > 3:
  124.             if argv[3].lower() != "incoming" and argv[3].lower() != "input" and \
  125.                argv[3].lower() != "output" and argv[3].lower() != "outgoing":
  126.                 raise ValueError()
  127.             if argv[3].lower().startswith("in"):
  128.                 direction = "incoming"
  129.             elif argv[3].lower().startswith("out"):
  130.                 direction = "outgoing"
  131.             else:
  132.                 direction = argv[3].lower()
  133.         if nargs < 3:
  134.             raise ValueError()
  135.         elif argv[2].lower() == "deny":
  136.             action = "default-deny"
  137.         elif argv[2].lower() == "allow":
  138.             action = "default-allow"
  139.         elif argv[2].lower() == "reject":
  140.             action = "default-reject"
  141.         else:
  142.             raise ValueError()
  143.  
  144.         action += "-%s" % (direction)
  145.  
  146.     if action == "allow" or action == "deny" or action == "reject" or \
  147.        action == "limit":
  148.         # set/strip
  149.         rule_direction = "in"
  150.         if nargs > 2 and (argv[2].lower() == "in" or \
  151.                           argv[2].lower() == "out"):
  152.             rule_direction = argv[2].lower()
  153.  
  154.         # strip out direction if not an interface rule
  155.         if nargs > 3 and argv[3] != "on" and (argv[2].lower() == "in" or \
  156.                                               argv[2].lower() == "out"):
  157.             rule_direction = argv[2].lower()
  158.             del argv[2]
  159.             nargs = len(argv)
  160.  
  161.         # strip out 'on' as in 'allow in on eth0 ...'
  162.         has_interface = False
  163.         if nargs > 2 and (argv.count('in') > 0 or argv.count('out') > 0):
  164.             err_msg = _("Invalid interface clause")
  165.  
  166.             if argv[2].lower() != "in" and argv[2].lower() != "out":
  167.                 raise UFWError(err_msg)
  168.             if nargs < 4 or argv[3].lower() != "on":
  169.                 raise UFWError(err_msg)
  170.  
  171.             del argv[3]
  172.             nargs = len(argv)
  173.             has_interface = True
  174.  
  175.         log_idx = 0
  176.         if has_interface and nargs > 4 and (argv[4].lower() == "log" or \
  177.                                             argv[4].lower() == 'log-all'):
  178.             log_idx = 4
  179.         elif nargs > 3 and (argv[2].lower() == "log" or \
  180.                            argv[2].lower() == 'log-all'):
  181.             log_idx = 2
  182.  
  183.         if log_idx > 0:
  184.             logtype = argv[log_idx].lower()
  185.             # strip out 'log' or 'log-all' and parse as normal
  186.             del argv[log_idx]
  187.             nargs = len(argv)
  188.  
  189.         if "log" in argv:
  190.             err_msg = _("Option 'log' not allowed here")
  191.             raise UFWError(err_msg)
  192.  
  193.         if "log-all" in argv:
  194.             err_msg = _("Option 'log-all' not allowed here")
  195.             raise UFWError(err_msg)
  196.  
  197.         if nargs < 3 or nargs > 14:
  198.             raise ValueError()
  199.  
  200.         rule_action = action
  201.         if logtype != "":
  202.             rule_action += "_" + logtype
  203.         rule = ufw.common.UFWRule(rule_action, "any", "any", \
  204.                                   direction=rule_direction)
  205.         if remove:
  206.             rule.remove = remove
  207.         elif insert_pos != "":
  208.             try:
  209.                 rule.set_position(insert_pos)
  210.             except Exception:
  211.                 raise
  212.         if nargs == 3:
  213.             # Short form where only app or port/proto is given
  214.             if ufw.applications.valid_profile_name(argv[2]):
  215.                 # Check if name collision with /etc/services. If so, use
  216.                 # /etc/services instead of application profile
  217.                 try:
  218.                     ufw.util.get_services_proto(argv[2])
  219.                 except Exception:
  220.                     type = "both"
  221.                     rule.dapp = argv[2]
  222.                     rule.set_port(argv[2], "dst")
  223.             if rule.dapp == "":
  224.                 try:
  225.                     (port, proto) = ufw.util.parse_port_proto(argv[2])
  226.                 except UFWError:
  227.                     err_msg = _("Bad port")
  228.                     raise UFWError(err_msg)
  229.  
  230.                 if not re.match('^\d([0-9,:]*\d+)*$', port):
  231.                     if ',' in port or ':' in port:
  232.                         err_msg = _("Port ranges must be numeric")
  233.                         raise UFWError(err_msg)
  234.                     to_service = port
  235.  
  236.                 try:
  237.                     rule.set_protocol(proto)
  238.                     rule.set_port(port, "dst")
  239.                     type = "both"
  240.                 except UFWError:
  241.                     err_msg = _("Bad port")
  242.                     raise UFWError(err_msg)
  243.         elif nargs % 2 != 0:
  244.             err_msg = _("Wrong number of arguments")
  245.             raise UFWError(err_msg)
  246.         elif not 'from' in argv and not 'to' in argv and not 'in' in argv and \
  247.              not 'out' in argv:
  248.             err_msg = _("Need 'to' or 'from' clause")
  249.             raise UFWError(err_msg)
  250.         else:
  251.             # Full form with PF-style syntax
  252.             keys = [ 'proto', 'from', 'to', 'port', 'app', 'in', 'out' ]
  253.  
  254.             # quick check
  255.             if argv.count("to") > 1 or \
  256.                argv.count("from") > 1 or \
  257.                argv.count("proto") > 1 or \
  258.                argv.count("port") > 2 or \
  259.                argv.count("in") > 1 or \
  260.                argv.count("out") > 1 or \
  261.                argv.count("app") > 2 or \
  262.                argv.count("app") > 0 and argv.count("proto") > 0:
  263.                 err_msg = _("Improper rule syntax")
  264.                 raise UFWError(err_msg)
  265.  
  266.             i = 1
  267.             loc = ""
  268.             for arg in argv[1:]:
  269.                 if i % 2 == 0 and argv[i] not in keys:
  270.                     err_msg = _("Invalid token '%s'") % (argv[i])
  271.                     raise UFWError(err_msg)
  272.                 if arg == "proto":
  273.                     if i+1 < nargs:
  274.                         try:
  275.                             rule.set_protocol(argv[i+1])
  276.                         except Exception:
  277.                             raise
  278.                     else:
  279.                         err_msg = _("Invalid 'proto' clause")
  280.                         raise UFWError(err_msg)
  281.                 elif arg == "in" or arg == "out":
  282.                     if i+1 < nargs:
  283.                         try:
  284.                             if arg == "in":
  285.                                 rule.set_interface("in", argv[i+1])
  286.                             elif arg == "out":
  287.                                 rule.set_interface("out", argv[i+1])
  288.                         except Exception:
  289.                             raise
  290.                     else:
  291.                         err_msg = _("Invalid '%s' clause") % (arg)
  292.                         raise UFWError(err_msg)
  293.                 elif arg == "from":
  294.                     if i+1 < nargs:
  295.                         try:
  296.                             faddr = argv[i+1].lower()
  297.                             if faddr == "any":
  298.                                 faddr = "0.0.0.0/0"
  299.                                 from_type = "any"
  300.                             else:
  301.                                 if ufw.util.valid_address(faddr, "6"):
  302.                                     from_type = "v6"
  303.                                 else:
  304.                                     from_type = "v4"
  305.                             rule.set_src(faddr)
  306.                         except Exception:
  307.                             raise
  308.                         loc = "src"
  309.                     else:
  310.                         err_msg = _("Invalid 'from' clause")
  311.                         raise UFWError(err_msg)
  312.                 elif arg == "to":
  313.                     if i+1 < nargs:
  314.                         try:
  315.                             saddr = argv[i+1].lower()
  316.                             if saddr == "any":
  317.                                 saddr = "0.0.0.0/0"
  318.                                 to_type = "any"
  319.                             else:
  320.                                 if ufw.util.valid_address(saddr, "6"):
  321.                                     to_type = "v6"
  322.                                 else:
  323.                                     to_type = "v4"
  324.                             rule.set_dst(saddr)
  325.                         except Exception:
  326.                             raise
  327.                         loc = "dst"
  328.                     else:
  329.                         err_msg = _("Invalid 'to' clause")
  330.                         raise UFWError(err_msg)
  331.                 elif arg == "port" or arg == "app":
  332.                     if i+1 < nargs:
  333.                         if loc == "":
  334.                             err_msg = _("Need 'from' or 'to' with '%s'") % \
  335.                                         (arg)
  336.                             raise UFWError(err_msg)
  337.  
  338.                         tmp = argv[i+1]
  339.                         if arg == "app":
  340.                             if loc == "src":
  341.                                 rule.sapp = tmp
  342.                             else:
  343.                                 rule.dapp = tmp
  344.                         elif not re.match('^\d([0-9,:]*\d+)*$', tmp):
  345.                             if ',' in tmp or ':' in tmp:
  346.                                 err_msg = _("Port ranges must be numeric")
  347.                                 raise UFWError(err_msg)
  348.  
  349.                             if loc == "src":
  350.                                 from_service = tmp
  351.                             else:
  352.                                 to_service = tmp
  353.                         try:
  354.                             rule.set_port(tmp, loc)
  355.                         except Exception:
  356.                             raise
  357.                     else:
  358.                         err_msg = _("Invalid 'port' clause")
  359.                         raise UFWError(err_msg)
  360.                 i += 1
  361.  
  362.             # Figure out the type of rule (IPv4, IPv6, or both) this is
  363.             if from_type == "any" and to_type == "any":
  364.                 type = "both"
  365.             elif from_type != "any" and to_type != "any" and \
  366.                  from_type != to_type:
  367.                 err_msg = _("Mixed IP versions for 'from' and 'to'")
  368.                 raise UFWError(err_msg)
  369.             elif from_type != "any":
  370.                 type = from_type
  371.             elif to_type != "any":
  372.                 type = to_type
  373.  
  374.     # Adjust protocol
  375.     if to_service != "" or from_service != "":
  376.         proto = ""
  377.         if to_service != "":
  378.             try:
  379.                 proto = ufw.util.get_services_proto(to_service)
  380.             except Exception:
  381.                 err_msg = _("Could not find protocol")
  382.                 raise UFWError(err_msg)
  383.         if from_service != "":
  384.             if proto == "any" or proto == "":
  385.                 try:
  386.                     proto = ufw.util.get_services_proto(from_service)
  387.                 except Exception:
  388.                     err_msg = _("Could not find protocol")
  389.                     raise UFWError(err_msg)
  390.             else:
  391.                 try:
  392.                     tmp = ufw.util.get_services_proto(from_service)
  393.                 except Exception:
  394.                     err_msg = _("Could not find protocol")
  395.                     raise UFWError(err_msg)
  396.                 if proto == "any" or proto == tmp:
  397.                     proto = tmp
  398.                 elif tmp == "any":
  399.                     pass
  400.                 else:
  401.                     err_msg = _("Protocol mismatch (from/to)")
  402.                     raise UFWError(err_msg)
  403.  
  404.         # Verify found proto with specified proto
  405.         if rule.protocol == "any":
  406.             rule.set_protocol(proto)
  407.         elif proto != "any" and rule.protocol != proto:
  408.             err_msg = _("Protocol mismatch with specified protocol %s") % \
  409.                         (rule.protocol)
  410.             raise UFWError(err_msg)
  411.  
  412.     # Verify protocol not specified with application rule
  413.     if rule and rule.protocol != "any" and \
  414.        (rule.sapp != "" or rule.dapp != ""):
  415.         app = ""
  416.         if rule.dapp:
  417.             app = rule.dapp
  418.         else:
  419.             app = rule.sapp
  420.         err_msg = _("Improper rule syntax ('%s' specified with app rule)") % \
  421.                    (rule.protocol)
  422.         raise UFWError(err_msg)
  423.  
  424.     return (action, rule, type, dryrun)
  425.  
  426.  
  427. def parse_application_command(argv):
  428.     '''Parse applications command. Returns tuple for action and profile name'''
  429.     name = ""
  430.     action = ""
  431.     dryrun = False
  432.     addnew = False
  433.  
  434.     if len(argv) < 3 or argv[1].lower() != "app":
  435.         raise ValueError()
  436.  
  437.     argv.remove("app")
  438.     nargs = len(argv)
  439.  
  440.     if len(argv) > 1 and argv[1].lower() == "--dry-run":
  441.         dryrun = True
  442.         argv.remove(argv[1])
  443.  
  444.     app_cmds = ['list', 'info', 'default', 'update']
  445.  
  446.     if not argv[1].lower() in app_cmds:
  447.         raise ValueError()
  448.     else:
  449.         action = argv[1].lower()
  450.  
  451.     if action == "info" or action == "update":
  452.         if nargs >= 4 and argv[2] == "--add-new":
  453.             addnew = True
  454.             argv.remove("--add-new")
  455.             nargs = len(argv)
  456.  
  457.         if nargs < 3:
  458.             raise ValueError()
  459.  
  460.         # Handle quoted name with spaces in it by stripping Python's ['...']
  461.         # list as string text.
  462.         name = str(argv[2]).strip("[']")
  463.  
  464.         if addnew:
  465.             action += "-with-new"
  466.  
  467.     if action == "list" and nargs != 2:
  468.         raise ValueError()
  469.  
  470.     if action == "default":
  471.         if nargs < 3:
  472.             raise ValueError()
  473.         if argv[2].lower() == "allow":
  474.             action = "default-allow"
  475.         elif argv[2].lower() == "deny":
  476.             action = "default-deny"
  477.         elif argv[2].lower() == "reject":
  478.             action = "default-reject"
  479.         elif argv[2].lower() == "skip":
  480.             action = "default-skip"
  481.         else:
  482.             raise ValueError()
  483.  
  484.     return (action, name, dryrun)
  485.  
  486.  
  487. def get_command_help():
  488.     '''Print help message'''
  489.     msg = _('''
  490. Usage: %(progname)s %(command)s
  491.  
  492. %(commands)s:
  493.  %(enable)-31s enables the firewall
  494.  %(disable)-31s disables the firewall
  495.  %(default)-31s set default policy
  496.  %(logging)-31s set logging to %(level)s
  497.  %(allow)-31s add allow %(rule)s
  498.  %(deny)-31s add deny %(rule)s
  499.  %(reject)-31s add reject %(rule)s
  500.  %(limit)-31s add limit %(rule)s
  501.  %(delete)-31s delete %(urule)s
  502.  %(insert)-31s insert %(urule)s at %(number)s
  503.  %(status)-31s show firewall status
  504.  %(statusnum)-31s show firewall status as numbered list of %(rules)s
  505.  %(statusverbose)-31s show verbose firewall status
  506.  %(show)-31s show firewall report
  507.  %(version)-31s display version information
  508.  
  509. %(appcommands)s:
  510.  %(applist)-31s list application profiles
  511.  %(appinfo)-31s show information on %(profile)s
  512.  %(appupdate)-31s update %(profile)s
  513.  %(appdefault)-31s set default application policy
  514. ''' % ({'progname': ufw.common.programName, \
  515.          'command': "COMMAND", \
  516.          'commands': "Commands", \
  517.          'enable': "enable", \
  518.          'disable': "disable", \
  519.          'default': "default ARG", \
  520.          'logging': "logging LEVEL", \
  521.          'level': "LEVEL", \
  522.          'allow': "allow ARGS", \
  523.          'rule': "rule", \
  524.          'deny': "deny ARGS", \
  525.          'reject': "reject ARGS", \
  526.          'limit': "limit ARGS", \
  527.          'delete': "delete RULE", \
  528.          'urule': "RULE", \
  529.          'insert': "insert NUM RULE", \
  530.          'number': "NUM", \
  531.          'status': "status", \
  532.          'statusnum': "status numbered", \
  533.          'rules': "RULES", \
  534.          'statusverbose': "status verbose", \
  535.          'show': "show ARG", \
  536.          'version': "version", \
  537.          'appcommands': "Application profile commands", \
  538.          'applist': "app list", \
  539.          'appinfo': "app info PROFILE", \
  540.          'profile': "PROFILE", \
  541.          'appupdate': "app update PROFILE", \
  542.          'appdefault': "app default ARG"}))
  543.  
  544.     return (msg)
  545.  
  546.  
  547. class UFWFrontend:
  548.     '''UI'''
  549.     def __init__(self, dryrun, backend_type="iptables"):
  550.         if backend_type == "iptables":
  551.             try:
  552.                 self.backend = UFWBackendIptables(dryrun)
  553.             except Exception:
  554.                 raise
  555.         else:
  556.             raise UFWError("Unsupported backend type '%s'" % (backend_type))
  557.  
  558.         self._init_input_strings()
  559.  
  560.     def _init_input_strings(self):
  561.         '''Initialize input strings for translations'''
  562.         self.no = _("n")
  563.         self.yes = _("y")
  564.         self.yes_full = _("yes")
  565.  
  566.     def set_enabled(self, enabled):
  567.         '''Toggles ENABLED state in of <config_dir>/ufw/ufw.conf'''
  568.         res = ""
  569.  
  570.         str = "no"
  571.         if enabled:
  572.             str = "yes"
  573.  
  574.         changed = False
  575.         if (enabled and not self.backend._is_enabled()) or \
  576.            (not enabled and self.backend._is_enabled()):
  577.             changed = True
  578.  
  579.         # Update the config files when toggling enable/disable
  580.         if changed:
  581.             try:
  582.                 self.backend.set_default(self.backend.files['conf'], \
  583.                                          "ENABLED", str)
  584.             except UFWError, e:
  585.                 error(e.value)
  586.  
  587.         error_str = ""
  588.         if enabled:
  589.             try:
  590.                 self.backend.start_firewall()
  591.             except UFWError, e:
  592.                 if changed:
  593.                     error_str = e.value
  594.  
  595.             if error_str != "":
  596.                 # Revert config files when toggling enable/disable and
  597.                 # firewall failed to start
  598.                 try:
  599.                     self.backend.set_default(self.backend.files['conf'], \
  600.                                              "ENABLED", "no")
  601.                 except UFWError, e:
  602.                     error(e.value)
  603.  
  604.                 # Report the error
  605.                 error(error_str)
  606.  
  607.             res = _("Firewall is active and enabled on system startup")
  608.         else:
  609.             try:
  610.                 self.backend.stop_firewall()
  611.             except UFWError, e:
  612.                 error(e.value)
  613.  
  614.             res = _("Firewall stopped and disabled on system startup")
  615.  
  616.         return res
  617.  
  618.     def set_default_policy(self, policy, direction):
  619.         '''Sets default policy of firewall'''
  620.         res = ""
  621.         try:
  622.             res = self.backend.set_default_policy(policy, direction)
  623.             if self.backend._is_enabled():
  624.                 self.backend.stop_firewall()
  625.                 self.backend.start_firewall()
  626.         except UFWError, e:
  627.             error(e.value)
  628.  
  629.         return res
  630.  
  631.     def set_loglevel(self, level):
  632.         '''Sets log level of firewall'''
  633.         res = ""
  634.         try:
  635.             res = self.backend.set_loglevel(level)
  636.         except UFWError, e:
  637.             error(e.value)
  638.  
  639.         return res
  640.  
  641.     def get_status(self, verbose=False, show_count=False):
  642.         '''Shows status of firewall'''
  643.         try:
  644.             out = self.backend.get_status(verbose, show_count)
  645.         except UFWError, e:
  646.             error(e.value)
  647.  
  648.         return out
  649.  
  650.     def get_show_raw(self):
  651.         '''Shows raw output of firewall'''
  652.         try:
  653.             out = self.backend.get_running_raw()
  654.         except UFWError, e:
  655.             error(e.value)
  656.  
  657.         return out
  658.  
  659.     def set_rule(self, rule, ip_version):
  660.         '''Updates firewall with rule'''
  661.         res = ""
  662.         err_msg = ""
  663.         tmp = ""
  664.         rules = []
  665.  
  666.         if rule.dapp == "" and rule.sapp == "":
  667.             rules.append(rule)
  668.         else:
  669.             tmprules = []
  670.             try:
  671.                 if rule.remove:
  672.                     if ip_version == "v4":
  673.                         tmprules = self.backend.get_app_rules_from_system(rule, False)
  674.                     elif ip_version == "v6":
  675.                         tmprules = self.backend.get_app_rules_from_system(rule, True)
  676.                     elif ip_version == "both":
  677.                         tmprules = self.backend.get_app_rules_from_system(rule, False)
  678.                         tmprules6 = self.backend.get_app_rules_from_system(rule, True)
  679.                         # Only add rules that are different by more than v6 (we
  680.                         # will handle 'ip_version == both' specially, below).
  681.                         for x in tmprules:
  682.                             for y in tmprules6:
  683.                                 prev6 = y.v6
  684.                                 y.v6 = False
  685.                                 if not x.match(y):
  686.                                     y.v6 = prev6
  687.                                     tmprules.append(y)
  688.                     else:
  689.                         err_msg = _("Invalid IP version '%s'") % (ip_version)
  690.                         raise UFWError(err_msg)
  691.  
  692.                     # Don't process removal of non-existing application rules
  693.                     if len(tmprules) == 0 and not self.backend.dryrun:
  694.                         tmp =  _("Could not delete non-existent rule")
  695.                         if ip_version == "v4":
  696.                             res = tmp
  697.                         elif ip_version == "v6":
  698.                             res = tmp + " (v6)"
  699.                         elif ip_version == "both":
  700.                             res = tmp + "\n" + tmp + " (v6)"
  701.                         return res
  702.  
  703.                     for tmp in tmprules:
  704.                         r = tmp.dup_rule()
  705.                         r.remove = rule.remove
  706.                         r.set_action(rule.action)
  707.                         r.set_logtype(rule.logtype)
  708.                         rules.append(r)
  709.                 else:
  710.                     rules = self.backend.get_app_rules_from_template(rule)
  711.                     # Reverse the order of rules for inserted rules, so they
  712.                     # are inserted in the right order
  713.                     if rule.position > 0:
  714.                         rules.reverse()
  715.             except Exception:
  716.                 raise
  717.  
  718.         count = 0
  719.         set_error = False
  720.         pos_err_msg = _("Invalid position '")
  721.         num_v4 = self.backend.get_rules_count(False)
  722.         num_v6 = self.backend.get_rules_count(True)
  723.         for i, r in enumerate(rules):
  724.             count = i
  725.             if r.position > num_v4 + num_v6:
  726.                 pos_err_msg += str(r.position) + "'"
  727.                 raise UFWError(pos_err_msg)
  728.             try:
  729.                 if self.backend.use_ipv6():
  730.                     if ip_version == "v4":
  731.                         if r.position > num_v4:
  732.                             pos_err_msg += str(r.position) + "'"
  733.                             raise UFWError(pos_err_msg)
  734.                         r.set_v6(False)
  735.                         tmp = self.backend.set_rule(r)
  736.                     elif ip_version == "v6":
  737.                         if r.position > num_v4:
  738.                             r.set_position(r.position - num_v4)
  739.                         elif r.position != 0 and r.position <= num_v4:
  740.                             pos_err_msg += str(r.position) + "'"
  741.                             raise UFWError(pos_err_msg)
  742.                         r.set_v6(True)
  743.                         tmp = self.backend.set_rule(r)
  744.                     elif ip_version == "both":
  745.                         user_pos = r.position # user specified position
  746.                         r.set_v6(False)
  747.                         if not r.remove and user_pos > num_v4:
  748.                 # The user specified a v6 rule, so try to find a
  749.                 # match in the v4 rules and use its position.
  750.                             p = self.backend.find_other_position( \
  751.                                 user_pos - num_v4 + count, True)
  752.                             if p > 0:
  753.                                 r.set_position(p)
  754.                             else:
  755.                                 # If not found, then add the rule
  756.                                 r.set_position(0)
  757.                         tmp = self.backend.set_rule(r)
  758.  
  759.                         # We need to readjust the position since the number
  760.                         # the number of ipv4 rules increased
  761.                         if not r.remove and user_pos > 0:
  762.                             num_v4 = self.backend.get_rules_count(False)
  763.                             r.set_position(user_pos + 1)
  764.  
  765.                         r.set_v6(True)
  766.                         if not r.remove and r.position > 0 and \
  767.                            r.position <= num_v4:
  768.                 # The user specified a v4 rule, so try to find a
  769.                 # match in the v6 rules and use its position.
  770.                             p = self.backend.find_other_position(r.position, \
  771.                                                                  False)
  772.                             if p > 0:
  773.                                 # Subtract count since the list is reversed
  774.                                 r.set_position(p - count)
  775.                             else:
  776.                                 # If not found, then add the rule
  777.                                 r.set_position(0)
  778.                         if tmp != "":
  779.                             tmp += "\n"
  780.  
  781.                         # Readjust position to send to set_rule
  782.                         if not r.remove and r.position > num_v4:
  783.                             r.set_position(r.position - num_v4)
  784.  
  785.                         tmp += self.backend.set_rule(r)
  786.                     else:
  787.                         err_msg = _("Invalid IP version '%s'") % (ip_version)
  788.                         raise UFWError(err_msg)
  789.                 else:
  790.                     if ip_version == "v4" or ip_version == "both":
  791.                         r.set_v6(False)
  792.                         tmp = self.backend.set_rule(r)
  793.                     elif ip_version == "v6":
  794.                         err_msg = _("IPv6 support not enabled")
  795.                         raise UFWError(err_msg)
  796.                     else:
  797.                         err_msg = _("Invalid IP version '%s'") % (ip_version)
  798.                         raise UFWError(err_msg)
  799.             except UFWError, e:
  800.                 err_msg = e.value
  801.                 set_error = True
  802.                 break
  803.  
  804.             if r.updated:
  805.                 warn_msg = _("Rule changed after normalization")
  806.                 warnings.warn(warn_msg)
  807.  
  808.         if not set_error:
  809.             # Just return the last result if no error
  810.             res += tmp
  811.         elif len(rules) == 1:
  812.             # If no error, and just one rule, error out
  813.             error(err_msg)
  814.         else:
  815.         # If error and more than one rule, delete the successfully added
  816.         # rules in reverse order
  817.             undo_error = False
  818.             indexes = range(count+1)
  819.             indexes.reverse()
  820.             for j in indexes:
  821.                 if count > 0 and rules[j]:
  822.                     backout_rule = rules[j].dup_rule()
  823.                     backout_rule.remove = True
  824.                     try:
  825.                         self.set_rule(backout_rule, ip_version)
  826.                     except Exception:
  827.                         # Don't fail, so we can try to backout more
  828.                         undo_error = True
  829.                         warn_msg = _("Could not back out rule '%s'") % \
  830.                                      r.format_rule()
  831.                         warn(warn_msg)
  832.  
  833.             err_msg += _("\nError applying application rules.")
  834.             if undo_error:
  835.                 err_msg += _(" Some rules could not be unapplied.")
  836.             else:
  837.                 err_msg += _(" Attempted rules successfully unapplied.")
  838.  
  839.             raise UFWError(err_msg)
  840.  
  841.         return res
  842.  
  843.     def do_action(self, action, rule, ip_version):
  844.         '''Perform action on rule. action, rule and ip_version are usually
  845.            based on return values from parse_command().
  846.         '''
  847.         res = ""
  848.         if action.startswith("logging-on"):
  849.             tmp = action.split('_')
  850.             if len(tmp) > 1:
  851.                 res = self.set_loglevel(tmp[1])
  852.             else:
  853.                 res = self.set_loglevel("on")
  854.         elif action == "logging-off":
  855.             res = self.set_loglevel("off")
  856.         elif action.startswith("default-"):
  857.             err_msg = _("Unsupported default policy")
  858.             tmp = action.split('-')
  859.             if len(tmp) != 3:
  860.                 raise UFWError(err_msg)
  861.             res = self.set_default_policy(tmp[1], tmp[2])
  862.         elif action == "status":
  863.             res = self.get_status()
  864.         elif action == "status-verbose":
  865.             res = self.get_status(True)
  866.         elif action == "show-raw":
  867.             res = self.get_show_raw()
  868.         elif action == "status-numbered":
  869.             res = self.get_status(False, True)
  870.         elif action == "enable":
  871.             res = self.set_enabled(True)
  872.         elif action == "disable":
  873.             res = self.set_enabled(False)
  874.         elif action == "reload":
  875.             if self.backend._is_enabled():
  876.                 self.set_enabled(False)
  877.                 self.set_enabled(True)
  878.                 res = _("Firewall reloaded")
  879.             else:
  880.                 res = _("Firewall not enabled (skipping reload)")
  881.         elif action == "allow" or action == "deny" or action == "reject" or \
  882.              action == "limit":
  883.             # allow case insensitive matches for application rules
  884.             if rule.dapp != "":
  885.                 try:
  886.                     tmp = self.backend.find_application_name(rule.dapp)
  887.                     if tmp != rule.dapp:
  888.                         rule.dapp = tmp
  889.                         rule.set_port(tmp, "dst")
  890.                 except UFWError, e:
  891.                     # allow for the profile being deleted (LP: #407810)
  892.                     if not rule.remove:
  893.                         error(e.value)
  894.                     if not ufw.applications.valid_profile_name(rule.dapp):
  895.                         err_msg = _("Invalid profile name")
  896.                         raise UFWError(err_msg)
  897.  
  898.             if rule.sapp != "":
  899.                 try:
  900.                     tmp = self.backend.find_application_name(rule.sapp)
  901.                     if tmp != rule.sapp:
  902.                         rule.sapp = tmp
  903.                         rule.set_port(tmp, "dst")
  904.                 except UFWError, e:
  905.                     # allow for the profile being deleted (LP: #407810)
  906.                     if not rule.remove:
  907.                         error(e.value)
  908.                     if not ufw.applications.valid_profile_name(rule.sapp):
  909.                         err_msg = _("Invalid profile name")
  910.                         raise UFWError(err_msg)
  911.  
  912.             res = self.set_rule(rule, ip_version)
  913.         else:
  914.             err_msg = _("Unsupported action '%s'") % (action)
  915.             raise UFWError(err_msg)
  916.  
  917.         return res
  918.  
  919.     def set_default_application_policy(self, policy):
  920.         '''Sets default application policy of firewall'''
  921.         res = ""
  922.         try:
  923.             res = self.backend.set_default_application_policy(policy)
  924.         except UFWError, e:
  925.             error(e.value)
  926.  
  927.         return res
  928.  
  929.     def get_application_list(self):
  930.         '''Display list of known application profiles'''
  931.         names = self.backend.profiles.keys()
  932.         names.sort()
  933.         rstr = _("Available applications:")
  934.         for n in names:
  935.             rstr += "\n  %s" % (n)
  936.         return rstr
  937.  
  938.     def get_application_info(self, pname):
  939.         '''Display information on profile'''
  940.         names = []
  941.         if pname == "all":
  942.             names = self.backend.profiles.keys()
  943.             names.sort()
  944.         else:
  945.             if not ufw.applications.valid_profile_name(pname):
  946.                 err_msg = _("Invalid profile name")
  947.                 raise UFWError(err_msg)
  948.             names.append(pname)
  949.  
  950.         rstr = ""
  951.         for name in names:
  952.             if not self.backend.profiles.has_key(name) or \
  953.                not self.backend.profiles[name]:
  954.                 err_msg = _("Could not find profile '%s'") % (name)
  955.                 raise UFWError(err_msg)
  956.  
  957.             if not ufw.applications.verify_profile(name, \
  958.                self.backend.profiles[name]):
  959.                 err_msg = _("Invalid profile")
  960.                 raise UFWError(err_msg)
  961.  
  962.             rstr += _("Profile: %s\n") % (name)
  963.             rstr += _("Title: %s\n") % (ufw.applications.get_title(\
  964.                                         self.backend.profiles[name]))
  965.  
  966.             rstr += _("Description: %s\n\n") % \
  967.                                             (ufw.applications.get_description(\
  968.                                              self.backend.profiles[name]))
  969.  
  970.             ports = ufw.applications.get_ports(self.backend.profiles[name])
  971.             if len(ports) > 1 or ',' in ports[0]:
  972.                 rstr += _("Ports:")
  973.             else:
  974.                 rstr += _("Port:")
  975.  
  976.             for p in ports:
  977.                 rstr += "\n  %s" % (p)
  978.  
  979.             if name != names[len(names)-1]:
  980.                 rstr += "\n\n--\n\n"
  981.  
  982.         return ufw.util.wrap_text(rstr)
  983.  
  984.     def application_update(self, profile):
  985.         '''Refresh application profile'''
  986.         rstr = ""
  987.         allow_reload = True
  988.         trigger_reload = False
  989.  
  990.         try:
  991.             if self.backend.do_checks and ufw.util.under_ssh():
  992.                 # Don't reload the firewall if running under ssh
  993.                 allow_reload = False
  994.         except:
  995.             # If for some reason we get an exception trying to find the parent
  996.             # pid, err on the side of caution and don't automatically reload
  997.             # the firewall. LP: #424528
  998.             allow_reload = False
  999.  
  1000.         if profile == "all":
  1001.             profiles = self.backend.profiles.keys()
  1002.             profiles.sort()
  1003.             for p in profiles:
  1004.                 (tmp, found) = self.backend.update_app_rule(p)
  1005.                 if found:
  1006.                     if tmp != "":
  1007.                         tmp += "\n"
  1008.                     rstr += tmp
  1009.                     trigger_reload = found
  1010.         else:
  1011.             (rstr, trigger_reload) = self.backend.update_app_rule(profile)
  1012.             if rstr != "":
  1013.                 rstr += "\n"
  1014.  
  1015.         if trigger_reload and self.backend._is_enabled():
  1016.             if allow_reload:
  1017.                 try:
  1018.                     self.backend._reload_user_rules()
  1019.                 except Exception:
  1020.                     raise
  1021.                 rstr += _("Firewall reloaded")
  1022.             else:
  1023.                 rstr += _("Skipped reloading firewall")
  1024.  
  1025.         return rstr
  1026.  
  1027.     def application_add(self, profile):
  1028.         '''Refresh application profile'''
  1029.         rstr = ""
  1030.         policy = ""
  1031.  
  1032.         if profile == "all":
  1033.             err_msg = _("Cannot specify 'all' with '--add-new'")
  1034.             raise UFWError(err_msg)
  1035.  
  1036.         default = self.backend.defaults['default_application_policy']
  1037.         if default == "skip":
  1038.             ufw.util.debug("Policy is '%s', not adding profile '%s'" % \
  1039.                            (policy, profile))
  1040.             return rstr
  1041.         elif default == "accept":
  1042.             policy = "allow"
  1043.         elif default == "drop":
  1044.             policy = "deny"
  1045.         elif default == "reject":
  1046.             policy = "reject"
  1047.         else:
  1048.             err_msg = _("Unknown policy '%s'") % (default)
  1049.             raise UFWError(err_msg)
  1050.  
  1051.         args = [ 'ufw' ]
  1052.         if self.backend.dryrun:
  1053.             args.append("--dry-run")
  1054.  
  1055.         args += [ policy, profile ]
  1056.         try:
  1057.             (action, rule, ip_version, self.backend.dryrun) = \
  1058.                 parse_command(args)
  1059.         except Exception:
  1060.             raise
  1061.  
  1062.         rstr = self.do_action(action, rule, ip_version)
  1063.         return rstr
  1064.  
  1065.     def do_application_action(self, action, profile):
  1066.         '''Perform action on profile. action and profile are usually based on
  1067.            return values from parse_applications_command().
  1068.         '''
  1069.         res = ""
  1070.         if action == "default-allow":
  1071.             res = self.set_default_application_policy("allow")
  1072.         elif action == "default-deny":
  1073.             res = self.set_default_application_policy("deny")
  1074.         elif action == "default-reject":
  1075.             res = self.set_default_application_policy("reject")
  1076.         elif action == "default-skip":
  1077.             res = self.set_default_application_policy("skip")
  1078.         elif action == "list":
  1079.             res = self.get_application_list()
  1080.         elif action == "info":
  1081.             res = self.get_application_info(profile)
  1082.         elif action == "update" or action == "update-with-new":
  1083.             str1 = self.application_update(profile)
  1084.             str2 = ""
  1085.             if action == "update-with-new":
  1086.                 str2 = self.application_add(profile)
  1087.  
  1088.             if str1 != "" and str2 != "":
  1089.                 str1 += "\n"
  1090.             res = str1 + str2
  1091.         else:
  1092.             err_msg = _("Unsupported action '%s'") % (action)
  1093.             raise UFWError(err_msg)
  1094.  
  1095.         return res
  1096.  
  1097.     def continue_under_ssh(self):
  1098.         '''If running under ssh, prompt the user for confirmation'''
  1099.         proceed = True
  1100.         if self.backend.do_checks and ufw.util.under_ssh():
  1101.             prompt = _("Command may disrupt existing ssh connections.")
  1102.             prompt += _(" Proceed with operation (%(yes)s|%(no)s)? ") % \
  1103.                        ({'yes': self.yes, 'no': self.no})
  1104.             os.write(sys.stdout.fileno(), prompt)
  1105.             ans = sys.stdin.readline().lower().strip()
  1106.             if ans != "y" and ans != self.yes and ans != self.yes_full:
  1107.                 proceed = False
  1108.  
  1109.         return proceed
  1110.  
  1111.